/*
 * @Authoryour name
 * @Date2020-12-08 23:10:32
 * @LastEditTime2020-12-08 23:12:00
 * @LastEditorsyour name
 * @DescriptionIn User Settings Edit
 * @FilePath\STM32H750_FreeRTOS\Bsp\bsp_qspi\bsp_qspi_w25q64.c
 */ 
#include "bsp_qspi_w25q64.h"
#include "bsp_gpio.h"
#include <string.h>

#define BSP_QSPI_FLASH_PRINT_ENABLE   FALSE

static void _w25q64_gpio_init(void);
static void _w25q64_module_init(void);

static void _w25q64_reset(void);
static void _w25q64_exit_qpimode(void);
static void _w25q64_enter_qpimode(void);
static void _w25q64_wait_busy(void);
static void _w25q64_wait_ready(void);
static void _w25q64_write_status(uint8_t write_index, uint8_t data);
static void _w25q64_write_ctrl(boolean is_enable);
static void _w25q64_memorymapped(void);
static uint8_t _w25q64_read_status(uint8_t read_index);

static void _w25q64_print_status(void);

static QSPI_HandleTypeDef qspi_handle;
static uint8_t qspi_w25qxx_mode = W25QXX_MODE_SPI;
static uint8_t qspi_w25qxx_buf[W25Q64_SECTOR_SIZE] = {0};

void bsp_w25q64_page_prg(uint8_t *buffer, uint32_t prg_addr, uint32_t prg_size);
void bsp_w25q64_direct_write(uint8_t *buffer, uint32_t write_addr, uint32_t write_size);

uint8_t qspi_buffer_debug[200];
void bsp_w25q64_test(void)
{
    uint32_t index = 0;
    uint32_t address = 0x00000000;
    uint32_t size = 200;
    printf("W25Q64 BSP Test Start!\r\n");
    printf("===============================================\r\n");
    (void)memset(qspi_buffer_debug, 0, 200);
    printf("Read the data form qspi flash, the read address is: 0x%08x, the read size is: %d\r\n", address, size);
    bsp_gpio_set(GPIO_B0);
    bsp_w25q64_read(qspi_buffer_debug, address, size);
    bsp_gpio_clear(GPIO_B0);
    printf("The read data is:\r\n");
    for(index = 0; index < size; index += 8)
    {
        printf("data:");
        printf(" 0x%02x", qspi_buffer_debug[index]);
        printf(" 0x%02x", qspi_buffer_debug[index + 1]);
        printf(" 0x%02x", qspi_buffer_debug[index + 2]);
        printf(" 0x%02x", qspi_buffer_debug[index + 3]);
        printf(" 0x%02x", qspi_buffer_debug[index + 4]);
        printf(" 0x%02x", qspi_buffer_debug[index + 5]);
        printf(" 0x%02x", qspi_buffer_debug[index + 6]);
        printf(" 0x%02x", qspi_buffer_debug[index + 7]);
        printf("\r\n");
    }

    for(index = 0; index < size; ++index)
    {
        qspi_buffer_debug[index] = index & 0x0F;
    }
    printf("===============================================\r\n");
    printf("Write the data form qspi flash, the write address is: 0x%08x, the write size is: %d\r\n", address, size);
    bsp_gpio_set(GPIO_B0);
    bsp_w25q64_erase_sector(0);
    bsp_gpio_clear(GPIO_B0);
    bsp_gpio_set(GPIO_B0);
    bsp_w25q64_direct_write(qspi_buffer_debug, address, size);
    bsp_gpio_clear(GPIO_B0);
    printf("The write data is:\r\n");
    for(index = 0; index < size; index += 8)
    {
        printf("data:");
        printf(" 0x%02x", qspi_buffer_debug[index]);
        printf(" 0x%02x", qspi_buffer_debug[index + 1]);
        printf(" 0x%02x", qspi_buffer_debug[index + 2]);
        printf(" 0x%02x", qspi_buffer_debug[index + 3]);
        printf(" 0x%02x", qspi_buffer_debug[index + 4]);
        printf(" 0x%02x", qspi_buffer_debug[index + 5]);
        printf(" 0x%02x", qspi_buffer_debug[index + 6]);
        printf(" 0x%02x", qspi_buffer_debug[index + 7]);
        printf("\r\n");
    }

    printf("===============================================\r\n");
    printf("Second read the data form qspi flash, the read address is: 0x%08x, the read size is: %d\r\n", address, size);
    bsp_gpio_set(GPIO_B0);            
    bsp_w25q64_read(qspi_buffer_debug, address, size);
    bsp_gpio_clear(GPIO_B0);
    printf("The read data is:\r\n");
    for(index = 0; index < size; index += 8)
    {
        printf("data:");
        printf(" 0x%02x", qspi_buffer_debug[index]);
        printf(" 0x%02x", qspi_buffer_debug[index + 1]);
        printf(" 0x%02x", qspi_buffer_debug[index + 2]);
        printf(" 0x%02x", qspi_buffer_debug[index + 3]);
        printf(" 0x%02x", qspi_buffer_debug[index + 4]);
        printf(" 0x%02x", qspi_buffer_debug[index + 5]);
        printf(" 0x%02x", qspi_buffer_debug[index + 6]);
        printf(" 0x%02x", qspi_buffer_debug[index + 7]);
        printf("\r\n");
    }
}

static void _w25q64_print_status(void)
{
    printf("<debug info> FLASH Status 1: 0x%02x\r\n", _w25q64_read_status(1));
}

/**
 * @brief  qspi flash w25q64 initialize
 * @param  void
 * @retval void
 */
void bsp_w25q64_init(void)
{
    _w25q64_gpio_init();
    _w25q64_module_init();
    _w25q64_exit_qpimode();
    _w25q64_reset();
    _w25q64_enter_qpimode();
    // _w25q64_memorymapped();
}

/**
 * @brief  
 * @param  
 * @retval 
 */
void bsp_w25q64_memorymapped(void)
{
    _w25q64_memorymapped();
}

/**
 * @brief  qspi flash w25q64 gpio initialize
 * @param  void
 * @retval void
 */
static void _w25q64_gpio_init(void)
{
    QSPI_GPIO_CLK_EN();

    GPIO_InitTypeDef gpio_init;
    /* QSPI CS */
    gpio_init.Mode = GPIO_MODE_AF_PP;
    gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    gpio_init.Pull = GPIO_NOPULL;
    gpio_init.Alternate = GPIO_AF10_QUADSPI;
    gpio_init.Pin = QSPI_GPIO_CS_PIN;
    HAL_GPIO_Init(QSPI_GPIO_CS_PORT, &gpio_init);
    /* QSPI CLK */
    gpio_init.Alternate = GPIO_AF9_QUADSPI;
    gpio_init.Pin = QSPI_GPIO_CLK_PIN;
    HAL_GPIO_Init(QSPI_GPIO_PORT, &gpio_init);
    /* QSPI IO0 */
    gpio_init.Alternate = GPIO_AF10_QUADSPI;
    gpio_init.Pin = QSPI_GPIO_IO0_PIN;
    HAL_GPIO_Init(QSPI_GPIO_PORT, &gpio_init);
    /* QSPI IO1 */
    gpio_init.Alternate = GPIO_AF10_QUADSPI;
    gpio_init.Pin = QSPI_GPIO_IO1_PIN;
    HAL_GPIO_Init(QSPI_GPIO_PORT, &gpio_init);
    /* QSPI IO2 */
    gpio_init.Alternate = GPIO_AF9_QUADSPI;
    gpio_init.Pin = QSPI_GPIO_IO2_PIN;
    HAL_GPIO_Init(QSPI_GPIO_PORT, &gpio_init);
    /* QSPI IO3 */
    gpio_init.Alternate = GPIO_AF9_QUADSPI;
    gpio_init.Pin = QSPI_GPIO_IO3_PIN;
    HAL_GPIO_Init(QSPI_GPIO_PORT, &gpio_init);
}

/**
 * @brief  qspi flash w25q64 module (qspi) initialize
 * @param  void
 * @retval void
 */
static void _w25q64_module_init(void)
{
    QPSI_CLK_EN();

    qspi_handle.Instance                = QSPI_FALSH;
    if(HAL_QSPI_DeInit(&qspi_handle) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }

    qspi_handle.Init.ClockPrescaler     = 1;
    qspi_handle.Init.FifoThreshold      = 4;
    qspi_handle.Init.SampleShifting     = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
    qspi_handle.Init.FlashSize          = QSPI_FLASH_SIZE;
    qspi_handle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_5_CYCLE;
    qspi_handle.Init.ClockMode          = QSPI_CLOCK_MODE_0;
    qspi_handle.Init.FlashID            = QSPI_FLASH_ID_1;
    qspi_handle.Init.DualFlash          = QSPI_DUALFLASH_DISABLE;
    if(HAL_QSPI_Init(&qspi_handle) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
}

/**
 * @brief  qspi flash w25q64 memory map configguration
 * @param  void
 * @retval void
 */
static void _w25q64_memorymapped(void)
{
    QSPI_CommandTypeDef qspi_cmd;
    QSPI_MemoryMappedTypeDef qspi_mem_mapped;

    qspi_cmd.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
    qspi_cmd.AddressSize       = QSPI_ADDRESS_24_BITS;
    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    qspi_cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    qspi_cmd.Instruction = W25Q64_CMD_FAST_READ_QPI_QUAD;
    qspi_cmd.AddressMode = QSPI_ADDRESS_4_LINES;
    qspi_cmd.DataMode    = QSPI_DATA_4_LINES;
    qspi_cmd.DummyCycles = 8;

    qspi_mem_mapped.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    qspi_mem_mapped.TimeOutPeriod = 0;

    if(HAL_QSPI_MemoryMapped(&qspi_handle, &qspi_cmd, &qspi_mem_mapped) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
}

/**
 * @brief  w25q64 page(256 bytes) program, no 
 * @param  buffer, the wirte data buffer
 * @param  prg_addr, page program address, this value should be times of 256
 * @param  prg_size, less than 256
 * @retval 
 */
void bsp_w25q64_page_prg(uint8_t *buffer, uint32_t prg_addr, uint32_t prg_size)
{
    QSPI_CommandTypeDef cmd;

    if (prg_size > 256)
        return;

    if (qspi_w25qxx_mode)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        cmd.AddressMode     = QSPI_ADDRESS_4_LINES;
        cmd.DataMode        = QSPI_DATA_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
        cmd.AddressMode     = QSPI_ADDRESS_1_LINE;
        cmd.DataMode        = QSPI_DATA_1_LINE;
    }
    cmd.Instruction        = W25Q64_CMD_PAGE_PROGRAM;
    cmd.AddressSize        = QSPI_ADDRESS_24_BITS;
    cmd.Address            = prg_addr;
    cmd.NbData             = prg_size;

    cmd.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes     = 0x00;
    cmd.DummyCycles        = 0;
    cmd.DdrMode            = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;

    _w25q64_write_ctrl(TRUE);
    if (HAL_QSPI_Command(&qspi_handle, &cmd, 100) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
        return;
    }
    if (HAL_QSPI_Transmit(&qspi_handle, buffer, 100) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
        return;
    }
    _w25q64_wait_ready();
#if BSP_QSPI_FLASH_PRINT_ENABLE
    printf("Page program end!\r\n");
    _w25q64_print_status();
#endif
}

/**
 * @brief  
 * @param  
 * @retval 
 */
static void _w25q64_wait_busy(void)
{
    while((_w25q64_read_status(1) & W25QXX_BUSY_MASK) == W25QXX_BUSY_MASK)
        ;
}

/**
 * @brief  qspi flash w25q64 data read
 * @param  buffer[out], the buffer to store the read data
 * @param  read_addr[in], the read address
 * @param  read_size[in], the read sze
 * @retval void
 */
void bsp_w25q64_read(uint8_t *buffer, uint32_t read_addr, uint32_t read_size)
{
    QSPI_CommandTypeDef qspi_cmd;

    if(qspi_w25qxx_mode == W25QXX_MODE_QPI)
    {
        qspi_cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        qspi_cmd.AddressMode     = QSPI_ADDRESS_4_LINES;
        qspi_cmd.DataMode        = QSPI_DATA_4_LINES;
    }
    else
    {
        qspi_cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
        qspi_cmd.AddressMode     = QSPI_ADDRESS_1_LINE;
        qspi_cmd.DataMode        = QSPI_DATA_1_LINE;
    }
    
    qspi_cmd.AddressSize       = QSPI_ADDRESS_24_BITS;
    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    qspi_cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    qspi_cmd.Instruction       = W25Q64_CMD_FAST_READ_QPI_IO4;
    
    qspi_cmd.DummyCycles = 8; 
    qspi_cmd.Address     = read_addr;
    qspi_cmd.NbData      = read_size;

    if(HAL_QSPI_Command(&qspi_handle, &qspi_cmd, 10000) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }

    /* Read */
    if(HAL_QSPI_Receive(&qspi_handle, buffer, 10000) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
#if BSP_QSPI_FLASH_PRINT_ENABLE
    printf("Read end!\r\n");
    _w25q64_print_status();
#endif
}

/**
 * @brief  Directly write data to the w25q64.
 * @param  buffer[in], the write data buffer
 * @param  write_addr[in], the write address
 * @param  write_size[in], the write data size
 * @retval void
 */
void bsp_w25q64_direct_write(uint8_t *buffer, uint32_t write_addr, uint32_t write_size)
{
    uint32_t page_remain = 0;
    /*  */
    page_remain = W25Q64_PAGE_SIZE - (write_addr & (W25Q64_PAGE_SIZE - 1));
    /*  */
    if(write_size <= page_remain) 
    {
        write_size = page_remain;
    }
    while(1)
    {
        bsp_w25q64_page_prg(buffer, write_addr, page_remain);
        if(write_size == page_remain)
        {
            break;
        }
        else
        {
            buffer += page_remain;
            write_addr += page_remain;
            write_size -= page_remain;
            if(write_size > W25Q64_PAGE_SIZE)
            {
                page_remain = W25Q64_PAGE_SIZE;
            }
            else
            {
                page_remain = write_size;
            }
        }
    }
#if BSP_QSPI_FLASH_PRINT_ENABLE
    printf("Direct write end!\r\n");
    _w25q64_print_status();
#endif
}

/**
 * @brief  Write data to the w25q64 with data protection, only program the address that not programed.
 * @param  buffer[in], the write data buffer
 * @param  write_addr[in], the write address
 * @param  write_size[in], the write data size
 * @retval void
 */
void bsp_w25q64_write(uint8_t *buffer, uint32_t write_addr, uint32_t write_size)
{
    uint32_t index = 0;
    uint32_t sector_addr = 0;
    uint32_t sector_num = write_addr >> W25Q64_SECTOR_BIT_NUM;
    uint32_t sector_proc = write_addr & (W25Q64_SECTOR_SIZE - 1);
    uint32_t sector_remain = W25Q64_SECTOR_SIZE - sector_proc;
    uint8_t *w25q64_buffer = (uint8_t *)qspi_w25qxx_buf;
    
    if(write_size <= sector_remain) 
    {
        sector_remain = write_size;
    }
    for(;;)
    {
        sector_addr = sector_num >> W25Q64_SECTOR_BIT_NUM;
        /* Read current sector data, and find the index that current data is not programed. */
        bsp_w25q64_read(w25q64_buffer, sector_addr, W25Q64_SECTOR_SIZE);
        for(index = 0; index < sector_remain; ++index)
        {
            /* Judge if the address is programed, if yes, break */
            if(buffer[sector_proc + index] != 0xFF)
                break;
        }
        /* (index < sector_remain) indicates that this sector has been programed */
        if(index < sector_remain)
        {
            bsp_w25q64_erase_sector(sector_addr);
            /* In order to protect data, [0 - sector_proc) data should maintain original */
            (void)memcpy(&w25q64_buffer[sector_proc], buffer, sector_remain);
            bsp_w25q64_direct_write(w25q64_buffer, sector_addr, W25Q64_SECTOR_SIZE);
        }
        /* (index == sector_remain) indicates that this sector has not been programed */
        else
        {
            bsp_w25q64_direct_write(buffer, write_addr, sector_remain);
        }

        if(write_size == sector_remain)
        {
            break;
        }
        else
        {
            /* Only the frist write the sector_proc is not 0 but calculated in the start of this function */
            sector_proc = 0;

            sector_num++;
            buffer += sector_remain;
            write_addr += sector_remain;
            write_size -= sector_remain;
            if(write_size > W25Q64_SECTOR_SIZE)
            {
                sector_remain = W25Q64_SECTOR_SIZE;
            }
            else
            {
                sector_remain = write_size;
            }
        }
    }
}

/**
 * @brief  
 * @param  
 * @retval 
 */
void bsp_w25q64_erase_sector(uint32_t addr)
{
    QSPI_CommandTypeDef qspi_cmd;
    /* Enable the flash write */
    if(qspi_w25qxx_mode == W25QXX_MODE_QPI)
    {
        qspi_cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        qspi_cmd.AddressMode     = QSPI_ADDRESS_4_LINES;
    }
    else
    {
        qspi_cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
        qspi_cmd.AddressMode     = QSPI_ADDRESS_1_LINE;
    }
    
    qspi_cmd.AddressSize       = QSPI_ADDRESS_24_BITS;
    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    qspi_cmd.DdrMode           = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    qspi_cmd.Instruction = W25Q64_CMD_SECTOR_ERASE;
    
    qspi_cmd.DataMode    = QSPI_DATA_NONE;
    qspi_cmd.DummyCycles = 0;
    qspi_cmd.Address     = addr;

    _w25q64_write_ctrl(TRUE);
    _w25q64_wait_ready();
    if(HAL_QSPI_Command(&qspi_handle, &qspi_cmd, 10000) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
    _w25q64_wait_ready();
#if BSP_QSPI_FLASH_PRINT_ENABLE
    printf("Erase sector end!\r\n");
    _w25q64_print_status();
#endif
    
}

/**
 * @brief  
 * @param  
 * @retval 
 */
void bsp_w25q64_erase_chip(void)
{
    QSPI_CommandTypeDef cmd;

    if (qspi_w25qxx_mode == W25QXX_MODE_QPI)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    }
    cmd.Instruction = W25Q64_CMD_CHIP_ERASE;

    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00;

    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;

    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;

    cmd.DummyCycles = 0;

    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    _w25q64_write_ctrl(TRUE);
    _w25q64_wait_ready();
    if(HAL_QSPI_Command(&qspi_handle, &cmd, 10000) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
    _w25q64_wait_ready();
}



static void _w25q64_exit_qpimode(void)
{
    QSPI_CommandTypeDef qspi_cmd;

    qspi_cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    qspi_cmd.Instruction = W25Q64_CMD_EXIT_QPI_MODE;

    qspi_cmd.AddressMode = QSPI_ADDRESS_NONE;
    qspi_cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    qspi_cmd.Address = 0x00;

    qspi_cmd.DataMode = QSPI_DATA_NONE;
    qspi_cmd.NbData = 0;

    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    qspi_cmd.AlternateBytesSize = 0;
    qspi_cmd.AlternateBytes = 0x00;

    qspi_cmd.DummyCycles = 0;

    qspi_cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    HAL_QSPI_Command(&qspi_handle, &qspi_cmd, 100);

    qspi_w25qxx_mode = W25QXX_MODE_SPI;
}

static void _w25q64_enter_qpimode(void)
{
    uint8_t dat;

    QSPI_CommandTypeDef cmd;

    dat = _w25q64_read_status(2);
    if ((dat & W25QXX_QE_MASK) == 0x00)
    {
        _w25q64_write_ctrl(TRUE);
        dat |= W25QXX_QE_MASK;
        _w25q64_write_status(2, dat);
    }

    cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    cmd.Instruction = W25Q64_CMD_ENTER_QPI_MODE;

    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00;

    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;

    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;

    cmd.DummyCycles = 0;

    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    HAL_QSPI_Command(&qspi_handle, &cmd, 100);

    qspi_w25qxx_mode = W25QXX_MODE_QPI;

    cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    cmd.Instruction = W25QXX_SetReadParameters;
    cmd.DataMode = QSPI_DATA_4_LINES;
    cmd.NbData = 1;
    dat = 0x03 << 4;

    _w25q64_write_ctrl(TRUE);

    if (HAL_QSPI_Command(&qspi_handle, &cmd, 100) == HAL_OK)
    {
        HAL_QSPI_Transmit(&qspi_handle, &dat, 100);
    }
}

static uint8_t _w25q64_read_status(uint8_t read_index)
{
    uint8_t dat = 0;

    QSPI_CommandTypeDef cmd;

    if (qspi_w25qxx_mode)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        cmd.DataMode = QSPI_DATA_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
        cmd.DataMode = QSPI_DATA_1_LINE;
    }

    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00;

    cmd.NbData = 1;

    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;

    cmd.DummyCycles = 0;

    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    switch (read_index)
    {
    case 1:
        cmd.Instruction = W25QXX_ReadStatusReg1;
        break;
    case 2:
        cmd.Instruction = W25QXX_ReadStatusReg2;
        break;
    case 3:
        cmd.Instruction = W25QXX_ReadStatusReg3;
        break;
    default:
        cmd.Instruction = W25QXX_ReadStatusReg1;
        break;
    }
    if (HAL_QSPI_Command(&qspi_handle, &cmd, 100) == HAL_OK)
    {
        HAL_QSPI_Receive(&qspi_handle, &dat, 100);
    }

    return dat;
}

static void _w25q64_write_status(uint8_t write_index, uint8_t data)
{
    QSPI_CommandTypeDef cmd;

    if (qspi_w25qxx_mode)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        cmd.DataMode = QSPI_DATA_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
        cmd.DataMode = QSPI_DATA_1_LINE;
    }

    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00;

    cmd.NbData = 1;

    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;

    cmd.DummyCycles = 0;

    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    switch (write_index)
    {
    case 1:
        cmd.Instruction = W25QXX_WriteStatusReg1;
        break;
    case 2:
        cmd.Instruction = W25QXX_WriteStatusReg2;
        break;
    case 3:
        cmd.Instruction = W25QXX_WriteStatusReg3;
        break;
    default:
        cmd.Instruction = W25QXX_WriteStatusReg1;
        break;
    }
    if (HAL_QSPI_Command(&qspi_handle, &cmd, 100) == HAL_OK)
    {
        HAL_QSPI_Transmit(&qspi_handle, &data, 100);
    }
}

static void _w25q64_write_ctrl(boolean is_enable)
{
    QSPI_CommandTypeDef cmd;

    if (qspi_w25qxx_mode == W25QXX_MODE_QPI)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    }

    if (is_enable)
    {
        cmd.Instruction = W25Q64_CMD_WRITE_ENABLE;
    }
    else
    {
        cmd.Instruction = W25Q64_CMD_WRITE_DISABLE;
    }

    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0x00;

    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;

    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;

    cmd.DummyCycles = 0;

    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    if(HAL_QSPI_Command(&qspi_handle, &cmd, 100) != HAL_OK)
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
}

static void _w25q64_reset(void)
{
    QSPI_CommandTypeDef cmd;

    if (qspi_w25qxx_mode)
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    }
    else
    {
        cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    }
    cmd.Instruction = W25Q54_CMD_ENABLE_RESET;

    cmd.AddressMode = QSPI_ADDRESS_NONE;
    cmd.AddressSize = QSPI_ADDRESS_24_BITS;
    cmd.Address = 0;

    cmd.DataMode = QSPI_DATA_NONE;
    cmd.NbData = 0;

    cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    cmd.AlternateBytesSize = 0;
    cmd.AlternateBytes = 0x00;

    cmd.DummyCycles = 0;

    cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    _w25q64_wait_ready();
    if (HAL_QSPI_Command(&qspi_handle, &cmd, 100) == HAL_OK)
    {
        cmd.Instruction = W25Q54_CMD_RESET;
        if(HAL_QSPI_Command(&qspi_handle, &cmd, 100) != HAL_OK)
            ExprAssertCalled(__FILE__, __LINE__);
    }
    else
    {
        ExprAssertCalled(__FILE__, __LINE__);
    }
}


static void _w25q64_wait_ready(void)
{
    while((_w25q64_read_status(1) & W25QXX_BUSY_MASK) == W25QXX_BUSY_MASK)
        ;
}
